const express = require("express");
const fileUpload = require("express-fileupload");
const path = require("path");
const app = express();
const fs = require("fs");
const async = require("async");

const PORT = 8001;

const UPLOADS_DIR = path.resolve(__dirname, "uploads");

app.use(express.static(path.join(__dirname, "public")));
app.use("/files", express.static(path.join(__dirname, "/public/files.html")));

app.use(express.urlencoded({ extended: true, limit: "5mb" })); // 解析 URL 编码的请求数据
app.use(express.json({ limit: "5mb" })); // 解析 JSON 请求数据

// default options
app.use(
  fileUpload({
    // 解决中文乱码：https://www.jianshu.com/p/813d85daaf60
    createParentPath: true,
    defParamCharset: "utf8", // 添加utf8编码

    limits: { fileSize: 5 * 1024 * 1024 }, // 5M (上传大于次大小会自动截取掉)
    useTempFiles: true, // 使用临时模板目录
    tempFileDir: path.resolve(__dirname, "./uploads-tmp/")
  })
);

app.get("/download/:fileName", function (req, res) {
  const fileName = req.params.fileName;
  const filePath = path.resolve(UPLOADS_DIR, fileName);

  res.download(filePath, function (err) {
    if (err) {
      console.error("Error downloading file:", err);
      res.status(500).json({ type: "error", message: err?.message });
    }
  });
});

app.get("/filelist", function (req, res) {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 100;
  const startIndex = (page - 1) * limit;
  const endIndex = startIndex + limit;

  fs.readdir(UPLOADS_DIR, function (err, files) {
    if (err) {
      // console.error("Error reading directory:", err);
      res.status(500).json({ code: 500, message: err?.message, data: {} });
    } else {
      const totalFiles = files.length;
      const paginatedFiles = files.slice(startIndex, endIndex);

      res.json({
        code: 200,
        message: "success",
        data: {
          totalFiles,
          page,
          limit,
          files: paginatedFiles
        }
      });
    }
  });
});

app.post("/upload", (req, res) => {
  let sampleFile;
  let uploadPath;

  if (!req.files || Object.keys(req.files).length === 0) {
    res.status(400).json({
      code: 400,
      message: "No files were uploaded.",
      data: { uploaded: false }
    });
    return;
  }

  // console.log("req.files >>>", req.files); // eslint-disable-line

  sampleFile = req.files.sampleFile;

  uploadPath = path.join(UPLOADS_DIR, sampleFile.name);

  sampleFile.mv(uploadPath, function (err) {
    if (err) {
      return res.status(500).json({ type: "error", message: err });
    }

    res.json({ code: 200, message: "success", data: { uploaded: true } });
  });
});

app.post("/merge", (req, res) => {
  const fileName = req.body.fileName;
  const totalChunks = req.body.totalChunks;

  const uploadPath = path.join(UPLOADS_DIR, fileName);

  mergeChunks(uploadPath, totalChunks, (err) => {
    if (err) {
      return res
        .status(500)
        .json({ code: 500, message: "error", data: { merged: false } });
    }
    res.json({ code: 200, message: "success", data: { merged: true } });
  });
  // const uploadPath = path.resolve(UPLOADS_DIR, fileName);
});

// (node:18784) MaxListenersExceededWarning:
// Possible EventEmitter memory leak detected.
// 11 finish listeners added to [WriteStream]. Use emitter.setMaxListeners()

function mergeChunks(uploadPath, totalChunks, callback) {
  const writeStream = fs.createWriteStream(uploadPath);
  let mergedChunks = 0;

  writeStream.on("error", function (err) {
    callback(err);
  });

  writeStream.on("finish", function () {
    deleteTempChunks(uploadPath, totalChunks, function (err) {
      if (err) {
        console.log("Error deleting temporary chunk files:", err);
      }
      callback(null);
    });
  });

  const chunkIndices = Array.from({ length: totalChunks }, (_, i) => i + 1);

  // Here we use eachLimit to only process a limited number of chunks at a time
  async.eachLimit(
    chunkIndices,
    8,
    function (chunkIndex, done) {
      const readStream = fs.createReadStream(
        `${uploadPath}_temp_${chunkIndex}`
      );
      readStream.pipe(writeStream, { end: false });
      readStream.on("end", function () {
        mergedChunks++;
        if (mergedChunks === totalChunks) {
          writeStream.end();
        }
        done();
      });
      readStream.on("error", function (err) {
        writeStream.end();
        callback(err);
      });
    },
    function (err) {
      if (err) {
        console.error("Error", err);
        callback(err);
      } else {
        callback(err);
      }
    }
  );
}

// function mergeChunks(uploadPath, totalChunks, callback) {
//   const writeStream = fs.createWriteStream(uploadPath);
//   let mergedChunks = 0;

//   writeStream.on("error", function (err) {
//     callback(err);
//   });

//   writeStream.on("finish", function () {
//     deleteTempChunks(uploadPath, totalChunks, function (err) {
//       if (err) {
//         console.log("Error deleting temporary chunk files:", err);
//       }
//       callback(null);
//     });
//   });

//   for (let i = 1; i <= totalChunks; i++) {
//     const readStream = fs.createReadStream(`${uploadPath}_temp_${i}`);
//     readStream.pipe(writeStream, { end: false });
//     readStream.on("end", function () {
//       mergedChunks++;
//       if (mergedChunks === totalChunks) {
//         writeStream.end();
//       }
//     });
//     readStream.on("error", function (err) {
//       writeStream.end();
//       callback(err);
//     });
//   }
// }

function deleteTempChunks(uploadPath, totalChunks, callback) {
  let deletedChunks = 0;
  for (let i = 1; i <= totalChunks; i++) {
    fs.unlinkSync(`${uploadPath}_temp_${i}`, function (err) {
      if (err) {
        callback(err);
      }
      deletedChunks++;
      if (deletedChunks === totalChunks) {
        callback(null);
      }
    });
  }
}

app.delete("/delete/:fileName", function (req, res) {
  const fileName = req.params.fileName;
  const filePath = path.resolve(UPLOADS_DIR, fileName);

  fs.unlink(filePath, function (err) {
    if (err) {
      console.error("Error deleting file:", err);
      res.status(500).json({
        code: 500,
        message: err?.message,
        data: {
          deleted: false
        }
      });
    } else {
      res.json({
        code: 200,
        message: "File deleted",
        data: {
          deleted: true
        }
      });
    }
  });
});

app.listen(PORT, function () {
  console.log("Express server listening on port ", PORT); // eslint-disable-line
});
